﻿using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Linq;
using CoreLibrary;
using CoreLibrary.Repo;
using Entities.DataBase.Obj;
using Entities.DataBase.Persistent;

namespace Entities.DataBase.Impl {

    public class ProductStockRepository : BaseRepository<Stock>, IProductStockRepository {

        private static readonly Func<DataContext, int, Stock>
           LinqGetStock = CompiledQuery.Compile((DataContext context, int id)
                => context.GetTable<Stock>().SingleOrDefault(o => o.Id == id));

        private static readonly Func<DataContext, int, string, Stock>
           LinqGetStockByLocationProductId = CompiledQuery.Compile((DataContext context, int location, string productId)
                => context.GetTable<Stock>().SingleOrDefault(o => o.Location == location && o.ProductId.Equals(productId)));

        private static readonly Func<DataContext,IQueryable<Stock>>
           LinqGetStocks = CompiledQuery.Compile((DataContext context)
                => context.GetTable<Stock>());

        private static readonly Func<DataContext, IQueryable<StockInfo>>
           LinqGetStockInfos = CompiledQuery.Compile((DataContext context) => (
                from stock   in context.GetTable<Stock>()
                join product in context.GetTable<Product>()
                on stock.ProductId equals product.ProductId 
                select new StockInfo {
                    Id          = stock.Id,
                    ProductId   = stock.ProductId,
                    ProductName = product.CName,
                    Location    = stock.Location,
                    Stock       = stock.Count,
                    Price       = (double)stock.Price,
                    Postage     = (double)stock.Postage,
                    PriceCurrency = stock.PriceCurrency,
                    PostageCurrency = stock.PostageCurrency
                }
         ));

        private static readonly Func<DataContext, int, IQueryable<StockInfo>>
           LinqGetStocksByLocation = CompiledQuery.Compile((DataContext context, int location) => (
                from stock   in context.GetTable<Stock>().Where(s => s.Location == location)
                join product in context.GetTable<Product>()
                on stock.ProductId equals product.ProductId
                select new StockInfo {
                    Id          = stock.Id,
                    ProductId   = stock.ProductId,
                    ProductName = product.CName,
                    Location    = stock.Location,
                    Stock       = stock.Count,
                    Price       = (double)stock.Price,
                    Postage     = (double)stock.Postage,
                    PriceCurrency = stock.PriceCurrency,
                    PostageCurrency = stock.PostageCurrency
                }
        ));

        private static readonly Func<DataContext,string,IQueryable<StockInfo>>
           LinqGetStocksByProductId = CompiledQuery.Compile((DataContext context, string productId) => (
               from stock   in context.GetTable<Stock>().Where(s => s.ProductId.Equals(productId))
               join product in context.GetTable<Product>().Where(p => p.ProductId.Equals(productId))
               on stock.ProductId equals product.ProductId
               select new StockInfo {
                   Id          = stock.Id,
                   ProductId   = stock.ProductId,
                   ProductName = product.CName,
                   Location    = stock.Location,
                   Stock       = stock.Count,
                   Price       = (double)stock.Price,
                   Postage     = (double)stock.Postage,
                   PriceCurrency = stock.PriceCurrency,
                   PostageCurrency = stock.PostageCurrency
               }
           ));

        public override string TableName {
            get {
                return "D_PRODUCT_STOCK";
            }
        }
        
        public IOrderRepository Orders { get; set; }
        public IPurchaseRecordRepository PurchaseRecords { get; set; }
        public IOrderRecordRepository OrderRecords       { get; set; }
        
        public Stock GetStock(int stockId) {
            return Execute(context => LinqGetStock(context, stockId));
        }

        public Stock GetStock(string productId, int location) {
            return Execute(context => LinqGetStockByLocationProductId(context, location, productId));
        }

        public List<StockInfo> ListAllStocks() {
            return Execute(context => LinqGetStockInfos(context).ToList());
        }

        public List<StockInfo> ListStocksByLocation(int location) {
            return Execute(context => LinqGetStocksByLocation(context, location).ToList());
        }

        public List<StockInfo> ListStocksByProductId(string productId) {
            return Execute(context => LinqGetStocksByProductId(context, productId).ToList());
        }

        public void IncreaseStocks(List<UpdateStockReq> requests) {
            Execute((context, ts) => {
                var stockIds = requests.Select(r => r.StockId).ToArray();
                var productStocks = LinqGetStocks(context).Where(s => stockIds.Contains(s.Id));
                foreach (var reduceStockReq in requests) {
                    var stock = productStocks.SingleOrDefault(s => s.Id == reduceStockReq.StockId);
                    if (stock != null) {
                        var count     = stock.Count;
                        stock.Count   = stock.Count + reduceStockReq.Number;
                        stock.Price   = (count * stock.Price   + (decimal)(reduceStockReq.Number * reduceStockReq.Cost)) /
                                        (count + reduceStockReq.Number);
                        stock.Postage = (count * stock.Postage + (decimal)(reduceStockReq.Number * reduceStockReq.Postage)) /
                                        (count + reduceStockReq.Number);
                        stock.DateUpdated = DateTime.UtcNow;
                    }
                }
                context.SubmitChanges();
                ts.Complete();
            });
        }

        public void ReduceStocks(List<UpdateStockReq> requests) {
            Execute((context, ts) => {
                var stockIds = requests.Select(r => r.StockId).ToArray();
                var productStocks = LinqGetStocks(context).Where(s => stockIds.Contains(s.Id));
                foreach (var reduceStockReq in requests) {
                    var productStock = productStocks.SingleOrDefault(s => s.Id == reduceStockReq.StockId);
                    if (productStock == null) {
                        continue;
                    }

                    productStock.Count = Math.Max(productStock.Count - reduceStockReq.Number, 0);
                    productStock.DateUpdated = DateTime.UtcNow;
                }
                context.SubmitChanges();
                ts.Complete();
            });
        }

        public void SyncStock(SyncStockReq request) {
            Execute((context, ts) => {
                var stock = LinqGetStockByLocationProductId(context, request.Location, request.ProductId);
                if (stock == null) {
                    context.GetTable<Stock>().InsertOnSubmit(new Stock {
                        ProductId = request.ProductId,
                        Location  = request.Location,
                        Count     = request.Count,
                        Price     = (decimal) request.Cost,
                        Postage   = (decimal) request.Postage,
                    });
                }
                else {
                    stock.Price       = (decimal) request.Cost;
                    stock.Postage     = (decimal) request.Postage;
                    stock.Count       = request.Count;
                    stock.DateUpdated = DateTime.UtcNow;
                }
                context.SubmitChanges();
                ts.Complete();
            });
        }

        public void CreateOrIncreaseStock(CreateStockReq request) {
            Execute((context, ts) => {
                var stock = LinqGetStockByLocationProductId(context, request.Location, request.ProductId);
                if (stock == null) {
                    context.GetTable<Stock>().InsertOnSubmit(new Stock {
                        Guid      = GuidUtil.GuidN(),
                        Location  = request.Location,
                        Postage   = (decimal)request.Postage,
                        Price     = (decimal)request.Price,
                        Count     = request.Count,
                        ProductId = request.ProductId
                    });
                } 
                else {
                    var count           = stock.Count + request.Count;
                    var currentprice    = stock.Price   * stock.Count;
                    var currentpostage  = stock.Postage * stock.Count;
                    
                    var incomingPrice   = (decimal)(request.Price   * request.Count);
                    var incomingPostage = (decimal)(request.Postage * request.Count);

                    stock.DateUpdated   = DateTime.UtcNow;
                    stock.Count         = count;
                    stock.Price         = (currentprice   + incomingPrice) / count;
                    stock.Postage       = (currentpostage + incomingPostage) / count;
                }
                context.SubmitChanges();
                ts.Complete();
            });
        }
       
    }
}